#pragma once

#include <rclcpp/rclcpp.hpp>
#include <opentelemetry/trace/provider.h>
#include <opentelemetry/exporters/ostream/span_exporter.h>
#include <opentelemetry/sdk/trace/simple_processor.h>
#include <opentelemetry/sdk/trace/tracer_provider.h>
#include <opentelemetry/sdk/trace/samplers/always_on.h>
#include <opentelemetry/sdk/resource/resource.h>
#include <opentelemetry/nostd/shared_ptr.h>
#include <opentelemetry/nostd/unique_ptr.h>

#include <chrono>
#include <thread>
#include <string>
#include <vector>
#include <memory>
#include <sys/resource.h>
#include <unistd.h>
#include <iomanip>
#include <sstream>

namespace trace_api = opentelemetry::trace;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace resource_sdk = opentelemetry::sdk::resource;
namespace nostd = opentelemetry::nostd;

// Performance monitoring utilities
struct PerformanceStats {
    // Timing statistics
    std::chrono::nanoseconds total_time;
    std::chrono::nanoseconds min_time;
    std::chrono::nanoseconds max_time;
    std::chrono::nanoseconds avg_time;
    size_t operation_count;
    
    // Memory statistics
    long memory_before_kb;
    long memory_after_kb;
    long memory_peak_kb;
    long memory_delta_kb;
    
    // CPU statistics
    long cpu_time_user_before;
    long cpu_time_system_before;
    long cpu_time_user_after;
    long cpu_time_system_after;
    long cpu_time_user_delta;
    long cpu_time_system_delta;
    
    void reset() {
        total_time = std::chrono::nanoseconds(0);
        min_time = std::chrono::nanoseconds::max();
        max_time = std::chrono::nanoseconds(0);
        avg_time = std::chrono::nanoseconds(0);
        operation_count = 0;
        
        memory_before_kb = 0;
        memory_after_kb = 0;
        memory_peak_kb = 0;
        memory_delta_kb = 0;
        
        cpu_time_user_before = 0;
        cpu_time_system_before = 0;
        cpu_time_user_after = 0;
        cpu_time_system_after = 0;
        cpu_time_user_delta = 0;
        cpu_time_system_delta = 0;
    }
    
    void calculateAverages() {
        if (operation_count > 0) {
            avg_time = std::chrono::nanoseconds(total_time.count() / operation_count);
        }
        memory_delta_kb = memory_after_kb - memory_before_kb;
        cpu_time_user_delta = cpu_time_user_after - cpu_time_user_before;
        cpu_time_system_delta = cpu_time_system_after - cpu_time_system_before;
    }
};

class PerformanceTestBase {
public:
    static long getCurrentMemoryKB() {
        struct rusage usage;
        getrusage(RUSAGE_SELF, &usage);
        return usage.ru_maxrss;
    }
    
    static std::pair<long, long> getCurrentCPUTime() {
        struct rusage usage;
        getrusage(RUSAGE_SELF, &usage);
        return {usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec,
                usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec};
    }
    
    static std::string formatDuration(std::chrono::nanoseconds duration) {
        auto ns = duration.count();
        if (ns < 1000) {
            return std::to_string(ns) + " ns";
        } else if (ns < 1000000) {
            return std::to_string(ns / 1000) + " μs";
        } else if (ns < 1000000000) {
            return std::to_string(ns / 1000000) + " ms";
        } else {
            return std::to_string(ns / 1000000000) + " s";
        }
    }
    
    static std::string formatMemory(long memory_kb) {
        if (memory_kb < 1024) {
            return std::to_string(memory_kb) + " KB";
        } else if (memory_kb < 1024 * 1024) {
            return std::to_string(memory_kb / 1024) + " MB";
        } else {
            return std::to_string(memory_kb / (1024 * 1024)) + " GB";
        }
    }
    
    static void printStats(const std::string& test_name, const PerformanceStats& stats) {
        std::cout << "\n=== " << test_name << " Performance Results ===" << std::endl;
        std::cout << "Operations: " << stats.operation_count << std::endl;
        std::cout << "Total Time: " << formatDuration(stats.total_time) << std::endl;
        std::cout << "Min Time: " << formatDuration(stats.min_time) << std::endl;
        std::cout << "Max Time: " << formatDuration(stats.max_time) << std::endl;
        std::cout << "Avg Time: " << formatDuration(stats.avg_time) << std::endl;
        std::cout << "Memory Before: " << formatMemory(stats.memory_before_kb) << std::endl;
        std::cout << "Memory After: " << formatMemory(stats.memory_after_kb) << std::endl;
        std::cout << "Memory Delta: " << formatMemory(stats.memory_delta_kb) << std::endl;
        std::cout << "Memory Peak: " << formatMemory(stats.memory_peak_kb) << std::endl;
        std::cout << "CPU User Time: " << stats.cpu_time_user_delta << " μs" << std::endl;
        std::cout << "CPU System Time: " << stats.cpu_time_system_delta << " μs" << std::endl;
        std::cout << "Throughput: " << std::fixed << std::setprecision(2) 
                  << (stats.operation_count * 1000000000.0 / stats.total_time.count()) << " ops/sec" << std::endl;
    }
    
    // Initialize OpenTelemetry tracing
    static void InitTracer() {
        auto exporter = std::unique_ptr<trace_sdk::SpanExporter>(
            new opentelemetry::exporter::trace::OStreamSpanExporter);
        
        auto processor = std::unique_ptr<trace_sdk::SpanProcessor>(
            new trace_sdk::SimpleSpanProcessor(std::move(exporter)));
        
        auto resource = resource_sdk::Resource::Create({
            {"service.name", "opentelemetry-performance-test"},
            {"service.version", "1.0.0"},
            {"service.instance.id", "performance-test-001"}
        });
        
        auto sampler = std::unique_ptr<trace_sdk::Sampler>(
            new trace_sdk::AlwaysOnSampler);
        
        auto provider = nostd::shared_ptr<trace_api::TracerProvider>(
            new trace_sdk::TracerProvider(std::move(processor), resource, std::move(sampler)));
        
        trace_api::Provider::SetTracerProvider(provider);
    }
};
